﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.IO;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace Platform
{
    // 客户端网络库，仅涉及Connect，Send，Recv。不支持监听等功能
    // 第一版，参考C++网络库开发，因为对C#还不够熟悉，逐步再修改吧。
    class SocketClient
    {
        private TcpClient       m_pTcpClient       = null;
        private NetworkStream   m_pNetworkStream   = null;
        private BinaryReader    m_Reader           = null;
        private BinaryWriter    m_Writer           = null;
        private int             m_nPackSize        = 0x00007FFF;

        public bool Connect(string strIP, int nPort)
        {
            Clear();
            try
            {
                m_pTcpClient = new TcpClient();
                m_pTcpClient.Connect(strIP, nPort);
            }
            catch (System.Exception ex) // 第一版，异常暂不处理
            {
                return false;
            }

            Thread.Sleep(500);

            m_pTcpClient.SendTimeout = 8;
            m_pTcpClient.SendBufferSize = 1024 * 64;
            m_pTcpClient.ReceiveTimeout = 10;
            m_pTcpClient.ReceiveBufferSize = 1024 * 64;

            m_pNetworkStream = m_pTcpClient.GetStream();
            m_Reader = new BinaryReader(m_pNetworkStream);
            m_Writer = new BinaryWriter(m_pNetworkStream);

            return true;
        }

        public void Send<T>(T obj)
        {
            // 先发包头，再发数据内容
            int     nHeaderLen  = 0;
            byte[] byData = Struct2Bytes(obj);
            byte[]  byHeader = new byte[4];
            int nDataSize = Marshal.SizeOf(obj);

            Debug.Assert(IsConnected());

            EncodeHeader(ref nHeaderLen, ref byHeader, byHeader.Length, nDataSize);

            m_Writer.Write(byHeader, 0, nHeaderLen);
            m_Writer.Flush();
            m_Writer.Write(byData, 0, nDataSize);
            m_Writer.Flush();
        }

        public byte[] Recv()
        {
            byte[] byHeader = new byte[4]; // HeaderSize
            byte[] byData;
            Debug.Assert(IsConnected());

            try
            {
                m_pNetworkStream.Read(byHeader, 0, 4);
            } catch (Exception e)
            {
                return null;
            }

            byData = new byte[byHeader[0]];
            m_pNetworkStream.Read(byData, 0, byHeader[0]);

            return byData;
        }

        public bool IsConnected()
        {
            if (m_pTcpClient == null)
                return false;

            return m_pTcpClient.Connected;
        }

        public void Disconnect()
        {
            Clear();
        }

        private void Clear()
        {
            if (m_Writer != null)
            {
                m_Writer.Close();
                m_Writer = null;
            }

            if (m_Reader != null)
            {
                m_Reader.Close();
                m_Reader = null;
            }

            if (m_pNetworkStream != null)
            {
                m_pNetworkStream.Close();
                m_pNetworkStream = null;
            }

            if (m_pTcpClient != null)
            {
                m_pTcpClient.Close();
                m_pTcpClient = null;
            }
        }

        private void EncodeHeader(ref int nHeaderLen, ref byte[] pHeaderBuffer, int HeaderBufferSize, int nUserDataLen)
        {
            if (nUserDataLen <= m_nPackSize)
            {
                pHeaderBuffer[0] = (byte)nUserDataLen;
                nHeaderLen = 2;
            }
            else
            {
                pHeaderBuffer[0] = (byte)((nUserDataLen >> 16) | 0x8000);
                pHeaderBuffer[1] = (byte)nUserDataLen;
                nHeaderLen = 4;
            }
        }

        private bool DecodeHeader(ref int nHeaderLen, ref int nUserDataLen, ref byte[] pbyData, int nDataLen)
        {
            if (nDataLen < 2)
                return false;

            if (pbyData[0] <= m_nPackSize)
            {
                nHeaderLen = 2;
                nUserDataLen = pbyData[0];
            }
            else
            {
                if (nDataLen < 4)
                    return false;

                nHeaderLen = 4;
                nUserDataLen = pbyData[0];
                nUserDataLen = nUserDataLen & 0x7FFF;
                nUserDataLen = (nUserDataLen << 16) | pbyData[1];
            }

            return true;
        }

        public static byte[] Struct2Bytes<T>(T obj)
        {
            int size = Marshal.SizeOf(obj);
            byte[] bytes = new byte[size];
            IntPtr arrPtr = Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0);
            Marshal.StructureToPtr(obj, arrPtr, true); 
            return bytes;
        }

        public static T Bytes2Struct<T>(byte[] bytes)
        {
            IntPtr arrPtr = Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0);
            return (T)Marshal.PtrToStructure(arrPtr, typeof(T));
        }
    }
}
